﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
using System.ComponentModel;
using System.Xml.Serialization;

namespace DailyRssDownload.Lib {
	[Serializable]
	public abstract class Feed {
		public Feed() {
			RecentlyDownloaded = new List<Entry>(0);
		}

		public string Url { get; set; }
		public string Title { get; set; }
		public string CssId { get; set; }
		public FeedTypes FeedType { get; set; }
		public int DisplayOrder { get; set; }
		public SummarySource.SummarySourcesEnum SummarySource { get; set; }
		public ContentSource.ContentSourcesEnum ContentSource { get; set; }
		DateTime? _dtLastDownloaded;
		public string ContentStartMarker { get; set; }
		public string ContentEndMarker { get; set; }

		[XmlIgnore]
		public IEnumerable<Entry> RecentlyDownloaded { get; set; }

		public DateTime? LastDownloaded {
			get { return _dtLastDownloaded; }
			set {
				// save last downloaded in LastLastDownloaded
				LastLastDownloaded = _dtLastDownloaded;
				_dtLastDownloaded = value;
			}
		}

		public DateTime? LastLastDownloaded { get; set; }

		public event ProgressChangedEventHandler ProgressChanged;

		public abstract IEnumerable<Entry> GetEntries(XDocument rssfeed);

		public static Feed CreateFeed(string strUrl, int intDisplayOrder) {
			if (String.IsNullOrEmpty(strUrl))
				return null;

			try {
				XDocument rssfeed = XDocument.Load(strUrl);

				FeedTypes feedType = GetFeedType(rssfeed.Root.Name.LocalName);

				Feed feed;
				
				if (feedType == FeedTypes.RSS2) {
					feed = new RssFeed(rssfeed);
				} else if (feedType == FeedTypes.Atom) {
					feed = new AtomFeed(rssfeed);
				} else {
					throw new ApplicationException("Unknown feed type.");
				}

				feed.Url = strUrl;
				feed.CssId = Entry.MakeOsSafeName(feed.Title);
				feed.DisplayOrder = intDisplayOrder;

				IEnumerable<Entry> lstEntries = feed.GetEntries(rssfeed);
				// get the five most recent posts
				IEnumerable<Entry> lstRecentEntries = from entry in lstEntries.Take(5)
													  select entry;

				double intAveragePostSize = lstRecentEntries.Average(i => i.Description.Length);

				// if the feeds posts are typically small then include the description field in the summary and download the content for the main article from the link
				if (intAveragePostSize < 1000) {
					feed.SummarySource = DailyRssDownload.Lib.SummarySource.SummarySourcesEnum.EntireDescription;
					feed.ContentSource = DailyRssDownload.Lib.ContentSource.ContentSourcesEnum.Download;
				// if the feeds posts are typically large then use them as the main article and don't display a summary
				} else {
					feed.SummarySource = DailyRssDownload.Lib.SummarySource.SummarySourcesEnum.None;
					feed.ContentSource = DailyRssDownload.Lib.ContentSource.ContentSourcesEnum.Summary;
				}

				return feed;
			} catch (Exception ex) {
				Log.LogError(ex);
				return null;
			}
		}

		private static FeedTypes GetFeedType(string strFirstElement) {
			switch (strFirstElement) {
				case "rss":
					return FeedTypes.RSS2;
				case "feed":
					return FeedTypes.Atom;
				default:
					throw new ApplicationException(String.Format("Unrecognized Feed Type: '{0}'", strFirstElement));
			}
		}

		public enum FeedTypes {
			RSS2,
			Atom
		}

		public void DownloadRecentEntries(string strDownloadToDirectory, bool blnOnlyDownloadNewContent) {
			XDocument xdocFeed = XDocument.Load(Url);

			// get all posts
			IEnumerable<Entry> lstEntries = GetEntries(xdocFeed);
			IEnumerable<Entry> lstRecentPosts = from entry in lstEntries
												where (entry.Published >= this.LastDownloaded) || this.LastDownloaded == null
												select entry;

			// for each recent post in the feed
			int intFeedsToDownload = lstRecentPosts.Count();
			int intCurrentFeed = 1;

			IList<Entry> lstRecentPostsToReturn = new List<Entry>();

			foreach (Entry objEntry in lstRecentPosts) {
				int intProgressAsPercent = (int)((intCurrentFeed / (decimal)intFeedsToDownload) * (decimal)100);
				ProgressChanged(this, new ProgressChangedEventArgs(intProgressAsPercent, "Downloading " + objEntry.Title));

				bool blnNewlyPublished = objEntry.Published > this.LastDownloaded;
				bool blnNeverDownloaded = this.LastDownloaded == null;

				// download item if 1. it is newly published or 2. we are supposed to download all content (not only new content)
				//		or 3. this is a new feed that we've never downloaded before
				if (blnNewlyPublished || !blnOnlyDownloadNewContent || blnNeverDownloaded) {
					objEntry.Download(strDownloadToDirectory);
					lstRecentPostsToReturn.Add(objEntry);
				}

				intCurrentFeed++;
			}

#if DEBUG
			LastDownloaded = DateTime.Now.AddDays(-7);
#else
			LastDownloaded = DateTime.Now;
#endif

			// note that we can't return lstRecentPosts, because we modified the contents with the Render() method and
			//		lstRecentPosts is a query
			RecentlyDownloaded = lstRecentPostsToReturn;
		}

		public XElement RenderAsXhtml() {
			return new XElement("div", new XAttribute("class", "DailyRss_Feed"), new XAttribute("id", this.CssId),
				new XElement("h2", this.Title),
				from entry in this.RecentlyDownloaded
				select entry.RenderAsXHtml());
		}

		public override bool Equals(object obj) {
			if (obj is Feed)
				return this.Url == ((Feed)obj).Url;

			return base.Equals(obj);
		}

		public override int GetHashCode() {
			return this.Url.GetHashCode();
		}
	}
}
